﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using ParserGenerator.Extensions;
using ParserGenerator.ParserConstruction;
using ParserGenerator.ScannerConstruction;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class GrammarDescriptionDumper
    {
        public void Dump(GrammarDescription grammar, string path)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (File.Exists(path))
                File.Delete(path);
            if (Directory.Exists(path))
                Directory.Delete(path, true);
            Directory.CreateDirectory(path);
            string baseName = Path.GetFileNameWithoutExtension(path);
            DumpGrammarDescription(grammar, path, baseName);
            DumpParserStateHyperLinks(grammar.ParserAutomaton, path, baseName);
            DumpScannerStateHyperLinks(grammar.ScannerAutomaton, path, baseName);
            DumpParserStateGraph(grammar.ParserAutomaton, path, baseName);
            DumpScannerStateGraph(grammar.ScannerAutomaton, path, baseName);
        }

        private void DumpGrammarDescription(GrammarDescription grammar, string path, string baseName)
        {
            if (grammar == null)
                throw new ArgumentNullException(nameof(grammar));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (baseName == null)
                throw new ArgumentNullException(nameof(baseName));
            string logFilePath = Path.Combine(path, baseName + ".log");
            using (StreamWriter writer = new StreamWriter(logFilePath))
            {
                string thick = new string('=', 40);
                writer.Write("Grammar");
                writer.Write("\n{0}", thick);
                writer.Write("\n{0}", grammar.Declaration);
                writer.Write("\n\nScanner");
                writer.Write("\n{0}", thick);
                writer.Write("\n{0}", grammar.ScannerAutomaton);
                writer.Write("\n\nParser");
                writer.Write("\n{0}", thick);
                writer.Write("\n{0}", grammar.ParserAutomaton);
                writer.Write("\n");
            }
        }

        private void DumpParserStateHyperLinks(ParserAutomaton parserAutomaton, string path, string baseName)
        {
            if (parserAutomaton == null)
                throw new ArgumentNullException(nameof(parserAutomaton));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (baseName == null)
                throw new ArgumentNullException(nameof(baseName));
            if (parserAutomaton.IsEmpty)
                return;
            string htmlFilePathh = Path.Combine(path, baseName + ".html");
            using (StreamWriter writer = new StreamWriter(htmlFilePathh))
                writer.Write("{0}\n", parserAutomaton.ToHtmlString());
        }

        private void DumpScannerStateHyperLinks(ScannerAutomaton scannerAutomaton, string path, string baseName)
        {
            if (scannerAutomaton == null)
                throw new ArgumentNullException(nameof(scannerAutomaton));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (baseName == null)
                throw new ArgumentNullException(nameof(baseName));
            if (scannerAutomaton.IsEmpty)
                return;
            string htmlFilePathh = Path.Combine(path, baseName + ".scanner.html");
            using (StreamWriter writer = new StreamWriter(htmlFilePathh))
                writer.Write("{0}\n", scannerAutomaton.ToHtmlString());
        }

        private void DumpParserStateGraph(ParserAutomaton parserAutomaton, string path, string baseName)
        {
            if (parserAutomaton == null)
                throw new ArgumentNullException(nameof(parserAutomaton));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (baseName == null)
                throw new ArgumentNullException(nameof(baseName));
            if (parserAutomaton.IsEmpty)
                return;
            if (parserAutomaton.States.Count > 30)
                return;
            string dotPath = Path.Combine(path, baseName + ".dot");
            string pdfPath = Path.Combine(path, baseName + ".pdf");
            using (StreamWriter writer = new StreamWriter(dotPath))
                writer.Write("{0}\n", parserAutomaton.ToGraphvizString());
            ConvertToPdf(dotPath, pdfPath);
        }

        private void DumpScannerStateGraph(ScannerAutomaton scannerAutomaton, string path, string baseName)
        {
            if (scannerAutomaton == null)
                throw new ArgumentNullException(nameof(scannerAutomaton));
            if (path == null)
                throw new ArgumentNullException(nameof(path));
            if (baseName == null)
                throw new ArgumentNullException(nameof(baseName));
            if (scannerAutomaton.IsEmpty)
                return;
            if (scannerAutomaton.States.Count > 30)
                return;
            string dotPath = Path.Combine(path, baseName + ".scanner.dot");
            string pdfPath = Path.Combine(path, baseName + ".scanner.pdf");
            using (StreamWriter writer = new StreamWriter(dotPath))
                writer.Write("{0}\n", scannerAutomaton.ToGraphvizString());
            ConvertToPdf(dotPath, pdfPath);
        }

        private static void ConvertToPdf(string dotPath, string pdfPath)
        {
            if (dotPath == null)
                throw new ArgumentNullException(nameof(dotPath));
            if (pdfPath == null)
                throw new ArgumentNullException(nameof(pdfPath));
            List<string> args = new List<string>();
            args.Add("-Tpdf");
            args.Add("-o");
            args.Add(pdfPath);
            args.Add(dotPath);
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = "dot";
            info.Arguments = args.JoinAsProgramArguments();
            info.WindowStyle = ProcessWindowStyle.Hidden;
            Process process = Process.Start(info);
            process.WaitForExit();
        }
    }
}
